package cn.bincker.mycaptcha.letter;

import cn.bincker.mycaptcha.CacheManager;
import cn.bincker.mycaptcha.basic.entity.MyQuestion;
import cn.bincker.mycaptcha.basic.properties.CacheConfigProperties;
import cn.bincker.mycaptcha.letter.entity.LetterAnswer;
import cn.bincker.mycaptcha.letter.entity.LetterCaptcha;
import cn.bincker.mycaptcha.utils.FileUtils;
import cn.bincker.mycaptcha.utils.RequestUtils;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;

@Slf4j
public class LetterCacheManagerImpl implements CacheManager<LetterCaptcha, LetterAnswer> {
    private final List<CacheInfo> cache;
    private final CacheConfigProperties config;
    private final File captchaPath;
    private final File answerPath;
    private final Random random;

    public LetterCacheManagerImpl(LetterCaptchaProperties properties) {
        config = properties.getCache();
        captchaPath = config.getCaptchaPath();
        answerPath = config.getAnswerPath();
        cache = new ArrayList<>(config.getMaxCacheItems());
        random = new Random();
        if(!captchaPath.exists() && !captchaPath.mkdirs()){
            throw new RuntimeException("创建缓存目录失败: path=" + captchaPath.getAbsolutePath());
        }
        if(!answerPath.exists() && !answerPath.mkdirs()){
            throw new RuntimeException("创建缓存目录失败: path=" + answerPath.getAbsolutePath());
        }
    }

    @Override
    public void put(MyQuestion<LetterCaptcha, LetterAnswer> question, BufferedImage... images) {
        var filename = FileUtils.generateNotExistsFilename(answerPath);
        File answerFile = new File(answerPath, filename), captchaImage = new File(captchaPath, filename + ".jpg");
        try(var answerOut = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(answerFile)))){
            answerOut.write(question.getAnswer().getCode());
            ImageIO.write(images[0], "jpg", captchaImage);
        } catch (IOException e) {
            deleteCache(answerFile, captchaImage, "异常");
            throw new RuntimeException("输出缓存失败", e);
        }
        question.getCaptcha().setImage(RequestUtils.getRequestPath() + captchaImage.getName());
        var cacheQuestion = question.clone();
        cacheQuestion.getCaptcha().setImage(captchaImage.getName());
        cache.add(new CacheInfo(
                System.currentTimeMillis() + config.getSurvivalTime(),
                cacheQuestion,
                answerFile,
                captchaImage
        ));
    }

    @Override
    public Optional<MyQuestion<LetterCaptcha, LetterAnswer>> shooting() {
        var position = random.nextInt(config.getMaxCacheItems());
        while(true){
            if(position < cache.size()) {
                var cacheInfo = cache.get(position);
                var question = cacheInfo.question;
                if (cacheInfo.expiredTime < System.currentTimeMillis()) {
                    cache.remove(position);
                    deleteCache(cacheInfo.answerFile, cacheInfo.imageFile, "过期");
                    position = random.nextInt(config.getMaxCacheItems());
                    continue;
                }
                return Optional.of(new MyQuestion<>(
                        new LetterCaptcha(RequestUtils.getRequestPath() + question.getCaptcha().getImage()),
                        question.getAnswer()
                ));
            }
            return Optional.empty();
        }
    }

    @Override
    public void reload() {
        var answerFiles = answerPath.listFiles();
        var imageFilenames = new HashSet<>(List.of(Optional.ofNullable(captchaPath.list()).orElse(new String[]{})));
        if(answerFiles != null){
            for (File answerFile : answerFiles) {
                var captchaImage = new File(captchaPath, answerFile.getName() + ".jpg");
                long createTime;
                try {
                    createTime = Files.readAttributes(Path.of(answerFile.getAbsolutePath()), BasicFileAttributes.class).creationTime().toMillis();
                    if(createTime + config.getSurvivalTime() < System.currentTimeMillis()){
                        deleteCache(answerFile, captchaImage, "过期");
                        continue;
                    }
                } catch (IOException e) {
                    log.warn("读取文件[{}]创建时间失败", answerFile.getAbsolutePath());
                    deleteCache(answerFile, captchaImage, "异常");
                    continue;
                }
                if(!captchaImage.exists()){
                    deleteCache(answerFile, captchaImage, "不完整");
                }else{
                    readCache(createTime, answerFile, captchaImage).ifPresent(cache::add);
                }
            }
        }
        if(!imageFilenames.isEmpty()){
            for (String imageFilename : imageFilenames) {
                new File(captchaPath, imageFilename).deleteOnExit();
            }
        }
    }

    private Optional<CacheInfo> readCache(long createTime, File answerFile, File captchaImage) {
        try(var reader = new BufferedReader(new InputStreamReader(new FileInputStream(answerFile)))){
            return Optional.of(new CacheInfo(
                    createTime + config.getSurvivalTime(),
                    new MyQuestion<>(
                            new LetterCaptcha(captchaImage.getName()),
                            new LetterAnswer(reader.readLine())
                    ),
                    captchaImage,
                    answerFile
            ));
        } catch (IOException e) {
            log.error("读取缓存失败：path={}", answerFile.getAbsolutePath());
            return Optional.empty();
        }
    }

    private void deleteCache(File answerFile, File captchaImage, String msg) {
        log.debug("验证码[{}]因{}被删除", answerFile.getName(), msg);
        if(answerFile.exists() && !answerFile.delete()){
            log.warn("删除缓存[{}]失败", answerFile.getAbsolutePath());
        }
        if(captchaImage.exists() && !captchaImage.delete()){
            log.warn("删除缓存[{}]失败", captchaImage.getAbsolutePath());
        }
    }

    @AllArgsConstructor
    private static class CacheInfo{
        private final long expiredTime;
        private final MyQuestion<LetterCaptcha, LetterAnswer> question;
        private final File imageFile;
        private final File answerFile;
    }
}
